/****************************************************************************
*
*					Functions of class ExactInference
*
****************************************************************************/

#include <string>
#include <vector>
#include <stdlib.h>
#include <cstdlib>
#include "ExactInference.h"

#define vectorMinResize		8

/****************************************************************************
*					ExactInference::ExactInference
*
*	Description:	Standard constructor with initialization of internal parameters
*	Parameters:		-
*	Returns:		-
*
****************************************************************************/
ExactInference::ExactInference (void) {
	// defult value for dumping parameter
	logZ = 0.0;
	domVarDep.clear();
	domVarDepSize.clear();
}


/****************************************************************************
*					ExactInference::~ExactInference
*
*	Description:	Standard destructor with removing all dynamic internal variables
*	Parameters:		-
*	Returns:		-
*
****************************************************************************/
ExactInference::~ExactInference (void) {
	
}


/****************************************************************************
*					ExactInference::run
*
*	Description:	Runs inference in the given PGM
*	Parameters:		Z - partition function value set by this inference run
*					pgmStruct - description of the PGM
*	Returns:		0
*
****************************************************************************/
int ExactInference::run (float& logZ, PGMStruct& pgmStruct) {
	unsigned int i, lastVarID;
	int j;
	// getting a connection between features domains and eliminting variables
	domVarDep.resize(pgmStruct.nVars);
	domVarDepSize.resize(pgmStruct.nVars, 0);
	// setting current size of domVarDep to 0-s
	for (i = 0; i < pgmStruct.nVars; i++) 
		domVarDepSize[i] = 0;
	// constucting domVarDep
	for (i = 0; i < pgmStruct.features.size(); i++) {
		// assuming that there is no feature with empty domain
		lastVarID = pgmStruct.features[i].varsIDs[pgmStruct.features[i].varsIDs.size() - 1];
		for (j = pgmStruct.features[i].varsIDs.size() - 2; j >= 0; j--) 
			if (pgmStruct.features[i].varsIDs[j] > lastVarID)
				lastVarID = pgmStruct.features[i].varsIDs[j];
		if (domVarDepSize[lastVarID] >= domVarDep[lastVarID].size())
			domVarDep[lastVarID].resize(domVarDepSize[lastVarID] + vectorMinResize);
		domVarDep[lastVarID][ domVarDepSize[lastVarID] ] = i;
		domVarDepSize[lastVarID]++;
	}
	
	/*// setting 'domVarDep' vectors to their real length
	for (i = 0; i < pgmStruct.nVars; i++) 
		if (domVarDepSize[lastVarID] < domVarDep[lastVarID].size())
			domVarDep[lastVarID].resize(domVarDepSize[lastVarID]);
	*/

	std::vector <unsigned int> vals;
	vals.resize(pgmStruct.nVars, 0);
	
	logZ = (float) std::log (countUnnormProb (vals, 0, pgmStruct));
	this->logZ = logZ;
	return 0;
}


/****************************************************************************
*					ExactInference::run
*
*	Description:	Runs inference conditional on a feature 'featureDescr'
*	Parameters:		expectedValue - expected value of the feature w.r.t. 
*						probability distribution defined by PGM
*					featureDescr - variables and their values (representation
*						of the simple feature)
*					pgmStruct - description of the PGM
*	Returns:		0
*
****************************************************************************/
int ExactInference::run (float& expectedValue, const std::vector < std::pair<unsigned int, unsigned int> >& featureDescr, PGMStruct& pgmStruct) {
	// We assume that logZ and domVarDep were already set
	return run (expectedValue, featureDescr, featureDescr.size(), pgmStruct);
}


/****************************************************************************
*					ExactInference::run
*
*	Description:	Runs inference conditional on a feature 'featureDescr'
*					with a given featureDescrSize
*	Parameters:		expectedValue - expected value of the feature w.r.t. 
*						probability distribution defined by PGM
*					featureDescr - variables and their values (representation
*						of the simple feature)
*					pgmStruct - description of the PGM
*	Returns:		0
*
****************************************************************************/
int ExactInference::run (float& expectedValue, const std::vector < std::pair<unsigned int, unsigned int> >& featureDescr, const unsigned int& featureDescrSize, PGMStruct& pgmStruct) {
	// We assume that logZ and domVarDep were already set
	std::vector <unsigned int> vals;
	vals.resize(pgmStruct.nVars, 0);
	expectedValue = (float) countUnnormProb (featureDescr, featureDescrSize, vals, 0, pgmStruct) / std::exp(logZ);
	return 0;
}


/****************************************************************************
*					ExactInference::run
*
*	Description:	Runs inference for evaluation of expected value of a diven 
*					feature
*	Parameters:		expectedValue - expected value of the feature w.r.t. 
*						probability distribution defined by PGM
*					feature - a feature for which to calculate expected value
*						w.r.t. probability distribution defined by pgmStruct
*					pgmStruct - description of the PGM
*	Returns:		0 - if everything went well
*					-1 - if an error occured
*
****************************************************************************/
int ExactInference::run (float& expectedValue, Feature& feature, std::vector <unsigned int>& varIDs, PGMStruct& pgmStruct) {
	std::vector <unsigned int> vals; 
	feature.getDesiredValues(vals);
	unsigned int numOfVals;
	numOfVals = vals.size();
	if (numOfVals == 0) {
		// running inference in general case - exactly running inference for a given feature
		//  DUMMY - for future implementation
		std::cout << "Error in ExactInference::run: dummy branch of the if clause is used.\n";
		return -1;
	} else {
		// checking that sizes are coinside
		if (numOfVals != varIDs.size()) {
			std::cout << "Warning in ExactInference::run: number of variabls is not equal to the number of desired values.\n";
			expectedValue = 0.0;
			return -1;
		}
		std::vector < std::pair<unsigned int, unsigned int> > featureDescr;
		featureDescr.resize(numOfVals);
		for (unsigned int i = 0; i < numOfVals; i++) {
			featureDescr[i].first = varIDs[i];
			featureDescr[i].second = vals[i];
		}
		return run (expectedValue, featureDescr, pgmStruct);
	}
	// should not get here - returning -1 indicating an error
	return -1;
}


/****************************************************************************
*					ExactInference::getLogLikelihood
*
*	Description:	Return likelihood of the data in the data set
*	Parameters:		dataSet - a set of samples for which to calculate the likelihood
*					pgmStruct - a description of the PGM
*	Returns:		float - log likelihood of the data set w.r.t. pgm
*
****************************************************************************/
float ExactInference::getLogLikelihood (DataSet& dataSet, PGMStruct& pgmStruct) {
	// Trivial case
	if (dataSet.data.size() < 1)
		return 0.0;
	// when there are some number of samples in the data set:
	unsigned int i, j;
	float curSum = 0.0;
	// for each feature in each domain in the PGM - calculating its (empirical probability * weight of this feature)
	for (i = 0; i < pgmStruct.features.size(); i++) {
		for (j = 0; j < pgmStruct.features[i].featsIDs.size(); j++) {
			if (pgmStruct.features[i].weightsIDs[j] >= 0)
				curSum += pgmStruct.weights[pgmStruct.features[i].weightsIDs[j]] * dataSet.calculateEmpiricalProb(*(pgmStruct.featureTypes[ pgmStruct.features[i].featsIDs[j] ]), pgmStruct.features[i].varsIDs);
			else
				curSum += dataSet.calculateEmpiricalProb(*(pgmStruct.featureTypes[ pgmStruct.features[i].featsIDs[j] ]), pgmStruct.features[i].varsIDs);
		}
	}
	// returning logLikelihood of the dataSet w.r.t. pgm
	return (curSum - logZ);
}


/****************************************************************************
*					ExactInference::setParameters
*
*	Description:	Set object patameters from environment
*	Parameters:		environment - a set of parameters read from config file
*	Returns:		0
*
****************************************************************************/
int ExactInference::setParameters (Environment &environment) {
	
	return 0;
}


/****************************************************************************
*					ExactInference::countUnnormProb
*
*	Description:	Recursive function for calculating Z using variable elimination (through recursion)
*	Parameters:		vals - values of variables in the PGM
*					curElem - index of variable in vals from which we can 
*						change values of vals
*					pgmStruct - description of the PGM
*	Returns:		float - partition function value (Z)
*
****************************************************************************/
double ExactInference::countUnnormProb (std::vector <unsigned int>& vals, const unsigned int& curElem, PGMStruct& pgmStruct) const {
	// basis of recursion
	if (curElem == vals.size())
		return 1.0;
	// going through all values of the variable curElem
	double pr = 0.0;
	double curPr;
	unsigned int i, j, k;
	for (i = 0; i < pgmStruct.numOfValues[curElem]; i++) {
		// setting current value of the variable curElem in vector vals
		vals[curElem] = i;
		// calculating features values that are in the domain 'domVarDep[curElem]'
		curPr = 0.0;
		for (j = 0; j < domVarDepSize[curElem]; j++) {
			// calculating value of features in the domain 'domVarDep[curElem][j]'
			for (k = 0; k < pgmStruct.features[ domVarDep[curElem][j] ].featsIDs.size(); k++) {
				// weight * feature value 
				if (pgmStruct.features[ domVarDep[curElem][j] ].weightsIDs[k] >= 0)
					curPr += pgmStruct.weights[ pgmStruct.features[ domVarDep[curElem][j] ].weightsIDs[k] ] * pgmStruct.featureTypes [ pgmStruct.features[ domVarDep[curElem][j] ].featsIDs[k] ]->calculate(pgmStruct.features[ domVarDep[curElem][j] ].varsIDs, vals);
				else
					curPr += pgmStruct.featureTypes [ pgmStruct.features[ domVarDep[curElem][j] ].featsIDs[k] ]->calculate(pgmStruct.features[ domVarDep[curElem][j] ].varsIDs, vals);
			}
		}
		// updating sum of unnormalized probabilities
		pr += std::exp (curPr) * countUnnormProb (vals, curElem + 1, pgmStruct);
	}
	return pr;
}


/****************************************************************************
*					ExactInference::countUnnormProb
*
*	Description:	Conditional on a given feature variant of the 
*					recursive function for calculating Z using variable elimination (through recursion)
*	Parameters:		featureDescr - description of the feature which expected
*						value is required
*					vals - values of variables in the PGM
*					curElem - index of variable in vals from which we can 
*						change values of vals
*					pgmStruct - description of the PGM
*	Returns:		float - partition function value (Z)
*
****************************************************************************/
double ExactInference::countUnnormProb (const std::vector < std::pair<unsigned int, unsigned int> >& featureDescr, const unsigned int& featureDescrSize, std::vector <unsigned int>& vals, const unsigned int& curElem, PGMStruct& pgmStruct) const {
	// basis of recursion
	if (curElem == vals.size())
		return 1.0;
	// going through all values of the variable curElem
	double pr = 0.0;
	double curPr;
	unsigned int i, j, k;
	// checking whether curElem is in featureDescription:
	i = 0;
	while ((i < featureDescrSize) && (featureDescr[i].first != curElem))
		i++;
	if (i == featureDescrSize) {
		// variable curElem is not in the feature - than we do a summation over all its values
		for (i = 0; i < pgmStruct.numOfValues[curElem]; i++) {
			// setting current value of the variable curElem in vector vals
			vals[curElem] = i;
			// calculating features values that are in the domain 'domVarDep[curElem]'
			curPr = 0.0;
			for (j = 0; j < domVarDepSize[curElem]; j++) {
				// calculating value of features in the domain 'domVarDep[curElem][j]'
				for (k = 0; k < pgmStruct.features[ domVarDep[curElem][j] ].featsIDs.size(); k++) {
					// weight * feature value 
					if (pgmStruct.features[ domVarDep[curElem][j] ].weightsIDs[k] >= 0)
						curPr += pgmStruct.weights[ pgmStruct.features[ domVarDep[curElem][j] ].weightsIDs[k] ] * pgmStruct.featureTypes [ pgmStruct.features[ domVarDep[curElem][j] ].featsIDs[k] ]->calculate(pgmStruct.features[ domVarDep[curElem][j] ].varsIDs, vals);
					else
						curPr += pgmStruct.featureTypes [ pgmStruct.features[ domVarDep[curElem][j] ].featsIDs[k] ]->calculate(pgmStruct.features[ domVarDep[curElem][j] ].varsIDs, vals);
				}
			}
			// updating sum of unnormalized probabilities
			pr += std::exp (curPr) * countUnnormProb (featureDescr, featureDescrSize, vals, curElem + 1, pgmStruct);
		}
	} else {
		// variable curElem is in the feature - calculating values of features for a particular values of the variable curElem
		vals[curElem] = featureDescr[i].second;
		// calculating features values that are in the domain 'domVarDep[curElem]'
		curPr = 0.0;
		for (j = 0; j < domVarDepSize[curElem]; j++) {
			// calculating value of features in the domain 'domVarDep[curElem][j]'
			for (k = 0; k < pgmStruct.features[ domVarDep[curElem][j] ].featsIDs.size(); k++) {
				// weight * feature value 
				if (pgmStruct.features[ domVarDep[curElem][j] ].weightsIDs[k] >= 0)
					curPr += pgmStruct.weights[ pgmStruct.features[ domVarDep[curElem][j] ].weightsIDs[k] ] * pgmStruct.featureTypes [ pgmStruct.features[ domVarDep[curElem][j] ].featsIDs[k] ]->calculate(pgmStruct.features[ domVarDep[curElem][j] ].varsIDs, vals);
				else
					curPr += pgmStruct.featureTypes [ pgmStruct.features[ domVarDep[curElem][j] ].featsIDs[k] ]->calculate(pgmStruct.features[ domVarDep[curElem][j] ].varsIDs, vals);
			}
		}
		// updating sum of unnormalized probabilities
		pr += std::exp (curPr) * countUnnormProb (featureDescr, featureDescrSize, vals, curElem + 1, pgmStruct);
	}
	return pr;
}
